﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Xml;
using System.Collections;

namespace UIElements.Service
{
    public static class IOService
    {
        public static void DeleteDirectory(string directoryPath)
        {
            if (DirectoryExists(directoryPath))
            {
                Directory.Delete(directoryPath, true);
            }
        }

        public static void CopyFile(string sourceFile, string destinationFile)
        {
            if (FileExists(sourceFile))
            {
                File.Copy(sourceFile, destinationFile, true);
            }
        }

        public static void CreateDirectory(string directoryPath)
        {
            if (!Directory.Exists(directoryPath))
            {
                Directory.CreateDirectory(directoryPath);
            }
        }

        public static string GetCombinedPath(string directoryPath, string fileName)
        {
            return Path.Combine(directoryPath, fileName);
        }

        public static void RenameDirectory(string oldDirectoryPath, string newDirectoryPath)
        {
            if (DirectoryExists(oldDirectoryPath))
            {
                Directory.Move(oldDirectoryPath, newDirectoryPath);
            }
        }

        public static void RenameFile(string oldFilePath, string newFilePath)
        {
            if (FileExists(oldFilePath))
            {
                File.Move(oldFilePath, newFilePath);
            }
        }

        public static string GetFullPath(string relativePath)
        {
            // get application data folder path
            string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);

            // create the full path from relative path
            string fullPath = Path.Combine(appDataPath, relativePath);

            // return the full path
            return fullPath;
        }

        public static void OpenFile(EnvDTE.DTE dte, string fileName)
        {
            // get the item operation
            var itemOp = dte.ItemOperations;

            // get the full path of the file name
            var fullPath = GetFullPath(fileName);

            // open the file name
            itemOp.OpenFile(fullPath, EnvDTEConstants.vsViewKindTextView);
        }

        public static string GetPatternTemplateFilePath(string patternTemplateName, PatternFileType patternFileType)
        {
            // determine the file extension
            string fileExtension = GetPatternFileExtension(patternFileType);

            // get relative path to the file
            string relativePath = @"PatternManagementExtension\" + patternTemplateName + @"\" + patternTemplateName + fileExtension;

            // create and return the full path from the relative path
            return GetFullPath(relativePath);
        }

        public static string GetPatternInstanceFilePath(string patternTemplateName, string patternInstanceName, PatternFileType patternFileType)
        {
            // determine the file extension
            string fileExtension = GetPatternFileExtension(patternFileType);

            // get relative path to the file
            string relativePath = string.Empty;
            if (patternFileType == PatternFileType.Code || patternFileType == PatternFileType.Template) // @changes
            {
                relativePath = @"PatternManagementExtension\" + patternTemplateName + @"\" + patternInstanceName + @"\" + patternInstanceName + fileExtension;
            }
            else
            {
                relativePath = @"PatternManagementExtension\" + patternTemplateName + @"\" + patternInstanceName + @"\" + patternTemplateName + fileExtension;
            }

            // create and return the full path from the relative path
            return GetFullPath(relativePath);
        }

        public static void GenerateCodeFromTempate(string spcFilePath)
        {
            // get the path to XVCL processor
            var xvclProcessorFilePath = GetXVCLProcessorFilePath();
    
            // specify file name and arguments
            var fileName = @"java.exe";
            var arguments = @"-jar " + @"""" + xvclProcessorFilePath + @"""" + " -B " + @"""" + spcFilePath + @"""";
            
            // execute the code generation command
            ExecuteCommand(fileName, arguments);
        }

        private static string GetXVCLProcessorFilePath()
        {
            return AppDomain.CurrentDomain.BaseDirectory + @"XVCL\xvcl.jar";
        }

        private static void ExecuteCommand(string fileName, string arguments)
        {
            // create process info based on the parameters
            var processInfo = new ProcessStartInfo(fileName, arguments)
            {
                CreateNoWindow = true,
                UseShellExecute = false,
                WindowStyle = ProcessWindowStyle.Hidden
            };

            // create new process with the above information
            var process = new Process()
            {
                StartInfo = processInfo
            };

            // start the process
            process.Start();
            process.WaitForExit();
        }

        private static string GetPatternFileExtension(PatternFileType patternFileType)
        {
            string fileExtension = string.Empty;

            switch (patternFileType)
            {
                case PatternFileType.XFrame:
                    fileExtension = @".xvcl";
                    break;
                case PatternFileType.Template:
                    fileExtension = @".template";
                    break;
                case PatternFileType.SPC:
                    fileExtension = @".config";
                    break;
                case PatternFileType.Code:
                    fileExtension = @".cs";
                    break;
                case PatternFileType.Manifest:
                    fileExtension = @".manifest";
                    break;
                default:
                    break;
            }
            return fileExtension;
        }

        public static bool FileExists(string filePath)
        {
            return File.Exists(filePath);
        }

        public static bool DirectoryExists(string directoryPath)
        {
            return Directory.Exists(directoryPath);
        }

        public static void DeleteFile(string filePath)
        {
            if (File.Exists(filePath))
            {
                File.Delete(filePath);
            }
        }

        public static void UpdateCodeFileNameInConfigFile(string instanceConfigFile, string patternInstanceName)
        {
            // load the config file to XML document
            XmlDocument configFileDoc = new XmlDocument();
            configFileDoc.Load(instanceConfigFile);

            // update the value of the pattern instance name
            var variableList = configFileDoc.GetElementsByTagName("x-frame");
            for (int i = 0; i < variableList.Count; i++)
            {
                var variable = variableList.Item(i);
                if (variable.Attributes["name"] != null && variable.Attributes["name"].Value != null)
                {
                    variable.Attributes["outfile"].Value = patternInstanceName + @".cs";
                    break;
                }
            }

            // save changes to the config file
            configFileDoc.Save(instanceConfigFile);
        }

        public static string GetBaseDirectory(string filePath)
        {
            return Path.GetDirectoryName(filePath);
        }

        public static void PopulateCodeToLocations(string codeFilePath, List<string> locationList)
        {
            // get the code file name
            var codeFileName = Path.GetFileName(codeFilePath);

            foreach (var location in locationList)
            {
                // get destination code file path
                var destCodeFilePath = GetCombinedPath(location, codeFileName);

                // copy the code file to the destination
                CopyFile(codeFilePath, destCodeFilePath);
            }
        }

        public static void CreateFile(string filePath)
        {
            if (!FileExists(filePath))
            {
                File.Create(filePath);
            }
        }

        public static void SetValue(string xmlFile, string nodeName, string attributeName, string attributeValue)
        {
            // load the XML document
            var xmlDoc = new XmlDocument();
            xmlDoc.Load(xmlFile);

            // get the node
            var node = xmlDoc.GetElementsByTagName(nodeName).Item(0);

            // set the value for the attribute
            node.Attributes[attributeName].Value = attributeValue;

            // save the changes
            xmlDoc.Save(xmlFile);
        }

        public static Hashtable LoadCodesAndVariablesFromConfig(string configFilePath)
        {
            // define the hashtable to store the variable
            Hashtable htbVariables = new Hashtable();

            // Load the config file into xml document
            var configDocument = new XmlDocument();
            configDocument.Load(configFilePath);

            // Load variables into hashtable
            var variableList = configDocument.GetElementsByTagName("set");
            for (int i = 0; i < variableList.Count; i++)
            {
                var variable = variableList.Item(i);
                htbVariables.Add(variable.Attributes["var"].Value, variable.Attributes["value"].Value);
            }

            // Load extra codes into hashtable
            var breakList = configDocument.GetElementsByTagName("insert");
            for (int i = 0; i < breakList.Count; i++)
            {
                var breakPoint = breakList.Item(i);
                htbVariables.Add(breakPoint.Attributes["break"].Value, breakPoint.Attributes["break"].Value);
            }

            // return the hashtable
            return htbVariables;
        }

        public static string GetFileContent(string filePath)
        {
            if (FileExists(filePath))
            {
                return File.ReadAllText(filePath);
            }
            else
            {
                return string.Empty;
            }
        }

        public static string GetXFrameFileContent(string templateContent, List<string> variableList)
        {
            var xvclContent = templateContent;

            // go through each variable in the hashtable
            foreach (var variable in variableList)
            {
                var xvclEquivalent = string.Empty;

                // if the key is a variable
                if (IsVariable(variable))
                {
                    xvclEquivalent = "<value-of expr=\"?@" + variable + "?\"/>";
                }
                // if the key is an extra code
                else
                {
                    xvclEquivalent = "<break name = \"" + variable + "\"></break>";
                }

                // replace the key in the template content by the XVCL equivalent
                xvclContent = xvclContent.Replace("@" + variable + "@", xvclEquivalent);            
            }

            return xvclContent;
        }

        public static void PutXVCLContent(string xframeFilePath, string xvclContent)
        {
            // load the x-frame file to the xml document
            var xmlDocument = new XmlDocument();
            xmlDocument.Load(xframeFilePath);

            // put the content to the file
            var xframeNode = xmlDocument.GetElementsByTagName("x-frame").Item(0);
            xframeNode.InnerXml = xvclContent;

            // save the x-frame file
            xmlDocument.Save(xframeFilePath);
        }

        public static bool IsVariable(string item)
        {
            return item!= null && item.StartsWith(@"$");
        }

        public static List<string> LoadCodesAndVariablesFromTemplate(string templateFilePath)
        {
            // define the list to store the variables
            var variableList = new List<string>();

            // get the content of the template file
            var templateFileContent = File.ReadAllText(templateFilePath);

            // add the variables from the template file to the hashtable 
            AddVariablesToList(ref variableList, templateFileContent, 0);

            // return the hashtable
            return variableList;
        }

        private static void AddVariablesToList(ref List<string> variableList, string templateFileContent, int startIndex)
        {
            if (startIndex >= templateFileContent.Length)
            {
                return;
            }

            // get the open @ index
            var openIndex = templateFileContent.IndexOf("@", startIndex);
            if (openIndex < 0)
            {
                return;
            }

            // get the closed @ index
            var closedIndex = templateFileContent.IndexOf("@", openIndex + 1);
            if (closedIndex < 0)
            {
                return;
            }

            // validate the length of the variable
            if (closedIndex - openIndex - 1 <= 0)
            {
                return;
            }

            // get the variable name
            var variableName = templateFileContent.Substring(openIndex + 1, closedIndex - openIndex - 1);

            // add the variable name to list
            if (!variableList.Contains(variableName))
            {
                variableList.Add(variableName);
            }

            // recursively add more variables
            AddVariablesToList(ref variableList, templateFileContent, closedIndex + 1);
        }

        public static void CleanUpConfigFile(string configFilePath)
        {
            // load the config file to an xml document
            var xmlDoc = new XmlDocument();
            xmlDoc.Load(configFilePath);
            
            // remove "set" node
            var setNodeList = xmlDoc.GetElementsByTagName("set");
            while (setNodeList.Count != 0)
            {
                var setNode = setNodeList.Item(0);
                setNode.ParentNode.RemoveChild(setNode);
            }

            // remove "insert" node
            var insertNodeList = xmlDoc.GetElementsByTagName("insert");
            while (insertNodeList.Count != 0)
            {
                var insertNode = insertNodeList.Item(0);
                insertNode.ParentNode.RemoveChild(insertNode);
            }

            // save changes
            xmlDoc.Save(configFilePath);
        }

        public static void AddVariablesToConfigFile(string configFilePath, List<string> variableList)
        {
            // load the config file
            var configFileXmlDoc = new XmlDocument();
            configFileXmlDoc.Load(configFilePath);

            // go through the list of variables and add to config file
            foreach (var variable in variableList)
            {
                if (IsVariable(variable))
                {
                    var xFrameNode = configFileXmlDoc.GetElementsByTagName("x-frame").Item(0);
                    var newNode = configFileXmlDoc.CreateElement("set");
                    var nodeNameAttribute = configFileXmlDoc.CreateAttribute("var");
                    nodeNameAttribute.Value = variable;
                    newNode.Attributes.Append(nodeNameAttribute);
                    var nodeValueAttribute = configFileXmlDoc.CreateAttribute("value");
                    nodeValueAttribute.Value = string.Empty;
                    newNode.Attributes.Append(nodeValueAttribute);
                    xFrameNode.PrependChild(newNode);
                }
                else
                {
                    var adaptNode = configFileXmlDoc.GetElementsByTagName("adapt").Item(0);
                    var newNode = configFileXmlDoc.CreateElement("insert");
                    var nodeNameAttribute = configFileXmlDoc.CreateAttribute("break");
                    nodeNameAttribute.Value = variable;
                    newNode.Attributes.Append(nodeNameAttribute);
                    newNode.InnerXml = string.Empty;
                    adaptNode.AppendChild(newNode);
                }
            }

            // save changes
            configFileXmlDoc.Save(configFilePath);
        }

        public static void AddVariableToConfigFile(string instanceConfigFilePath, string variableName, string variableValue)
        {
            // load the config file
            var configFileXmlDoc = new XmlDocument();
            configFileXmlDoc.Load(instanceConfigFilePath);

            // add the variable
            if (IsVariable(variableName))
            {
                var xFrameNode = configFileXmlDoc.GetElementsByTagName("x-frame").Item(0);
                var newNode = configFileXmlDoc.CreateElement("set");
                var nodeNameAttribute = configFileXmlDoc.CreateAttribute("var");
                nodeNameAttribute.Value = variableName;
                newNode.Attributes.Append(nodeNameAttribute);
                var nodeValueAttribute = configFileXmlDoc.CreateAttribute("value");
                nodeValueAttribute.Value = variableValue;
                newNode.Attributes.Append(nodeValueAttribute);
                xFrameNode.PrependChild(newNode);
            }
            else
            {
                var adaptNode = configFileXmlDoc.GetElementsByTagName("adapt").Item(0);
                var newNode = configFileXmlDoc.CreateElement("insert");
                var nodeNameAttribute = configFileXmlDoc.CreateAttribute("break");
                nodeNameAttribute.Value = variableName;
                newNode.Attributes.Append(nodeNameAttribute);
                newNode.InnerXml = variableValue;
                adaptNode.AppendChild(newNode);
            }

            // save changes
            configFileXmlDoc.Save(instanceConfigFilePath);
        }

        public static void RemoveVariableFromConfigFile(string instanceConfigFilePath, string variableName)
        {
            // load the config file
            var configFileXmlDoc = new XmlDocument();
            configFileXmlDoc.Load(instanceConfigFilePath);

            // add the variable
            if (IsVariable(variableName))
            {
                var setNodeList = configFileXmlDoc.GetElementsByTagName("set");
                for (int i = 0; i < setNodeList.Count; i++)
                {
                    if (setNodeList[i].Attributes["var"].Value == variableName)
                    {
                        setNodeList[i].ParentNode.RemoveChild(setNodeList[i]);
                        break;
                    }
                }
            }
            else
            {
                var insertNodeList = configFileXmlDoc.GetElementsByTagName("insert");
                for (int i = 0; i < insertNodeList.Count; i++)
                {
                    if (insertNodeList[i].Attributes["break"].Value == variableName)
                    {
                        insertNodeList[i].ParentNode.RemoveChild(insertNodeList[i]);
                        break;
                    }
                }
            }

            // save changes
            configFileXmlDoc.Save(instanceConfigFilePath);
        }
    }

    public enum PatternFileType
    {
        XFrame, SPC, Template, Code, Manifest
    }
}
